/*
 *文件作用: 测试驱动
 */

#include "leptjson.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>  // strtod():string to double
#include <string.h>

static int test_count = 0;  //总测试次数
static int test_pass = 0;   //测试通过次数
static int main_ret = 0;    //测试程序返回值，0 for success

//================================Start:  宏====================================
//基本比较宏定义，用以比较预期结果和实际结果是否一致
//参数:
//  - equality: 预期值和实际值判断相等的方式
//  - expect: 预期值
//  - actual: 实际值
//  - format: 发生错误时, expect 和 actual 的输出格式
#define EXPECT_EQ_BASE(equality, expect, actual, format)                                                               \
    do {                                                                                                               \
        test_count++;                                                                                                  \
        if (equality) {                                                                                                \
            test_pass++;                                                                                               \
        } else {                                                                                                       \
            fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);     \
            main_ret = 1;                                                                                              \
        }                                                                                                              \
    } while (0)

//比较预期结果和实际结果，指定以int作为格式化方式，基于基本比较宏。结果不匹配时打印错误位置和数值信息。
//参数:
//  - expect: 预期结果
//  - actual: 实际结果
#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d")

//同 EXPECT_EQ_INT, 但以 double 打印 expect 和 actual。
#define EXPECT_EQ_DOUBLE(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%e")

//同上, 以 string 打印 expect 和 actual
#define EXPECT_EQ_STRING(expect, actual, alength)                                                                      \
    EXPECT_EQ_BASE(sizeof(expect) - 1 == alength && memcmp(expect, actual, alength) == 0, expect, actual, "%s")

//比较 size_t类型 的数组长度是否一致
#define EXPECT_EQ_SIZE_T(expect, actual) EXPECT_EQ_BASE(((expect) == (actual)), (size_t)expect, (size_t)actual, "%zu")

//错误码测试宏，要求出现错误时能给出预期错误码，并将节点类型强制置为 LETP_NULL
//参数:
//  - error: 预期错误码
//  - json: json字符串
#define TEST_ERROR(error, json)                                                                                        \
    do {                                                                                                               \
        lept_value v;                                                                                                  \
        v.type = LEPT_BOOLEAN;                                                                                         \
        EXPECT_EQ_INT(error, lept_parse(&v, json));                                                                    \
        EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));                                                                   \
        lept_free(&v);                                                                                                 \
    } while (0)

//针对 true, false, null 的value解析测试宏，以 LEPT_NULL 为原始类型
//初始化节点类型，要求解析成功，并正确修改节点类型 参数:
//  - expect_type: 预期结果类型
//  - json: json字符串
#define TEST_LITERAL(expect_type, json)                                                                                \
    do {                                                                                                               \
        lept_value v;                                                                                                  \
        v.type = LEPT_NULL;                                                                                            \
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));                                                            \
        EXPECT_EQ_INT(expect_type, lept_get_type(&v));                                                                 \
        if (ISBOOLEANTYPE(&v)) {                                                                                       \
            if (ISTRUE(json)) {                                                                                        \
                EXPECT_EQ_INT(1, lept_get_boolean(&v));                                                                \
            }                                                                                                          \
        }                                                                                                              \
    } while (0)

// number 测试宏，检查解析是否成功,以及解析结果类型和解析结果数值
//参数:
//  - expect: 预期结果数字的值
//  - json: json字符串
#define TEST_NUMBER(expect, json)                                                                                      \
    do {                                                                                                               \
        lept_value v;                                                                                                  \
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));                                                            \
        EXPECT_EQ_INT(LEPT_NUMBER, lept_get_type(&v));                                                                 \
        EXPECT_EQ_DOUBLE(expect, lept_get_number(&v));                                                                 \
    } while (0)

// string 测试宏
#define TEST_STRING(expect_string, json)                                                                               \
    do {                                                                                                               \
        lept_value v;                                                                                                  \
        lept_init(&v);                                                                                                 \
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));                                                            \
        EXPECT_EQ_INT(LEPT_STRING, v.type);                                                                            \
        EXPECT_EQ_STRING(expect_string, v.union_struct.str.string, lept_get_string_length(&v));                        \
        /*PRINTSTRING(&v);*/                                                                                           \
        lept_free(&v);                                                                                                 \
    } while (0)

#define TEST_ARRAY(expect_size, json)                                                                                  \
    do {                                                                                                               \
        lept_value v;                                                                                                  \
        lept_init(&v);                                                                                                 \
        EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, json));                                                            \
        EXPECT_EQ_INT(LEPT_ARRAY, lept_get_type(&v));                                                                  \
        EXPECT_EQ_SIZE_T(expect_size, lept_get_array_size(&v));                                                        \
        printf("\nsize == %zu\n", v.union_struct.arr.size);                                                            \
        printArray(&v);                                                                                                \
        lept_free(&v);                                                                                                 \
    } while (0)

#ifndef PRINTSTRING(v)
#    define PRINTSTRING(v)                                                                                             \
        do {                                                                                                           \
            const char* p = (v)->union_struct.str.string;                                                              \
            printf("Now print the string in node: ");                                                                  \
            while (*p) {                                                                                               \
                printf("%x", *p);                                                                                      \
                ++p;                                                                                                   \
            }                                                                                                          \
            printf("\n");                                                                                              \
        } while (0)
#endif

// 打印数组内容
static void printArray(lept_value* v) {
    assert(lept_get_type(v) == LEPT_ARRAY);
    size_t i;
    for (i = 0; i < v->union_struct.arr.size; ++i) {
        lept_value* tmpV = lept_get_array_element(v, i);
        switch (lept_get_type(tmpV)) {
            case LEPT_NULL: printf("#%zu ==> type: null\n", i); break;
            case LEPT_BOOLEAN:
                printf("#%zu ==> type: boolean, value: %s\n", i, (lept_get_boolean(tmpV) == 1 ? "true" : "false"));
                break;
            case LEPT_NUMBER: printf("#%zu ==> type: number, value: %lf\n", i, lept_get_number(tmpV)); break;
            case LEPT_STRING: printf("#%zu ==> type: string, value: %s\n", i, lept_get_string(tmpV)); break;
            case LEPT_ARRAY:
                printf("#%zu ==> type: array, size: %zu\n", i, lept_get_array_size(tmpV));
                printArray(tmpV);
                break;
            case LEPT_OBJECT: printf("Object. To be continued\n"); break;
            default: break;
        }
    }
}

#if 0
#    ifndef PRINTARRAY(v)
#        define PRINTARRAY(v)                                                                                          \
            do {                                                                                                       \
                assert(lept_get_type(v) == LEPT_ARRAY);                                                                \
                size_t i;                                                                                              \
                for (i = 0; i < (v)->union_struct.arr.size; ++i) {                                                     \
                    lept_value* tmpV = lept_get_array_element(v, i);                                                   \
                    switch (lept_get_type(tmpV)) {                                                                     \
                        case LEPT_NULL: printf("#%zu ==> type: null\n", i); break;                                     \
                        case LEPT_BOOLEAN:                                                                             \
                            printf("#%zu ==> type: boolean, value: %s\n", i,                                           \
                                   (lept_get_boolean(tmpV) == 1 ? "true" : "false"));                                  \
                            break;                                                                                     \
                        case LEPT_NUMBER:                                                                              \
                            printf("#%zu ==> type: number, value: %lf\n", i, lept_get_number(tmpV));                   \
                            break;                                                                                     \
                        case LEPT_STRING: printf("#%zu ==> type: string, value: %s\n", i, lept_get_string(tmpV));      \
                            break;                                                                                     \
                        case LEPT_ARRAY:                                                                               \
                            printf("#%zu ==> type: array, size: %zu\n", i, lept_get_array_size(tmpV));                 \
                            PRINTARRAY(tmpV);                                                                          \
                            break;                                                                                     \
                        case LEPT_OBJECT: printf("Object. To be continued\n"); break;                                  \
                        default: break;                                                                                \
                    }                                                                                                  \
                }                                                                                                      \
            } while (0)
#    endif
#endif

//================================End:  宏====================================

//=============================Start: 字面量====================================
// true, false, null 字面量解析测试集合
static void test_parse_expect_literal() {
    TEST_LITERAL(LEPT_BOOLEAN, "true");   //测试true值解析
    TEST_LITERAL(LEPT_BOOLEAN, "false");  //测试false值解析
    TEST_LITERAL(LEPT_NULL, "null");      //测试null值解析
}

//非法字面量解析测试集合
static void test_parse_invalid_literal() {
    TEST_ERROR(LEPT_PARSE_ROOT_NOT_SINGULAR, "false f");  //测试多值输入
    TEST_ERROR(LEPT_PARSE_EXPECT_VALUE, " \t\n\r");       //测试无值输入
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "monday");       //测试非法输入
}

//字面量解析集合
static void test_parse_literal() {
    test_parse_expect_literal();   //测试合法字面量
    test_parse_invalid_literal();  //测试非法字面量
}
//===========================End: 字面量===============================

//===========================Start:  数字==============================
// number 解析测试集合
static void test_parse_expect_number() {
    TEST_NUMBER(0.0, "0");
    TEST_NUMBER(0.0, "-0");
    TEST_NUMBER(0.0, "-0.0");
    TEST_NUMBER(1.0, "1");
    TEST_NUMBER(-1.0, "-1");
    TEST_NUMBER(1.5, "1.5");
    TEST_NUMBER(-1.5, "-1.5");
    TEST_NUMBER(3.1416, "3.1416");
    TEST_NUMBER(1E10, "1E10");
    TEST_NUMBER(1e10, "1e10");
    TEST_NUMBER(1E+10, "1E+10");
    TEST_NUMBER(1E-10, "1E-10");
    TEST_NUMBER(-1E10, "-1E10");
    TEST_NUMBER(-1e10, "-1e10");
    TEST_NUMBER(-1E+10, "-1E+10");
    TEST_NUMBER(-1E-10, "-1E-10");
    TEST_NUMBER(1.234E+10, "1.234E+10");
    TEST_NUMBER(1.234E-10, "1.234E-10");
    TEST_NUMBER(0.0, "1e-10000"); /* must underflow */

    TEST_NUMBER(1.0000000000000002, "1.0000000000000002");            // the smallest number > 1
    TEST_NUMBER(4.9406564584124654e-324, "4.9406564584124654e-324");  // Min. subnormal positive double
    TEST_NUMBER(-4.9406564584124654e-324, "-4.9406564584124654e-324");
    TEST_NUMBER(2.2250738585072009e-308, "2.2250738585072009e-308");    // Max. subnormal double
    TEST_NUMBER(-2.2250738585072009e-308, "-2.2250738585072009e-308");  //
    TEST_NUMBER(2.2250738585072014e-308, "2.2250738585072014e-308");    // Min. normal positive double
    TEST_NUMBER(-2.2250738585072014e-308, "-2.2250738585072014e-308");
    TEST_NUMBER(1.7976931348623157e308, "1.7976931348623157e308");  // Max. Double
    TEST_NUMBER(-1.7976931348623157e308, "-1.7976931348623157e308");
}

//测试非法数字
static void test_parse_invalid_number() {
    /* invalid number */
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+0");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "+1");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, ".123");  // at least one digit before '.'
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "1.");    // at least one digit after '.'
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "INF");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "inf");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "NAN");
    TEST_ERROR(LEPT_PARSE_INVALID_VALUE, "nan");
}

static void test_parse_number() {
    test_parse_expect_number();   //测试合法数字
    test_parse_invalid_number();  //测试非法数字
}
//===========================End:  数字==============================

static void test_access_string() {
    lept_value v;
    lept_init(&v);
    lept_set_string(&v, "true", 4);
    EXPECT_EQ_STRING("true", lept_get_string(&v), 4);
}

static void test_parse_expect_string() {
    TEST_STRING("normal characters", "\"normal characters\"");    // normal characters
    TEST_STRING("quotation mark\"", "\"quotation mark\\\"\"");    // quotation mark
    TEST_STRING("reverse solidus\\", "\"reverse solidus\\\\\"");  // reverse solidus
    TEST_STRING("solidus\/", "\"solidus\\\/\"");                  // solidus
    TEST_STRING("backspace\b", "\"backspace\\b\"");               // backspace
    TEST_STRING("form feed\f", "\"form feed\\f\"");               // form feed
    TEST_STRING("line feed\n", "\"line feed\\n\"");               // line feed
    TEST_STRING("carriage return\r", "\"carriage return\\r\"");   // carriage return
    TEST_STRING("tab\t", "\"tab\\t\"");                           // tab
}
static void test_parse_invalid_string() {
    TEST_ERROR(LEPT_PARSE_INVALID_STRING_ESCAPE, "\"invalid_escape\\p\"");  // invalid escape \p
    TEST_ERROR(LEPT_PARSE_MISS_QUOTATION_MARK, "\"miss_quotation_mark");    // miss quotation mark
}

//测试预期的 utf-8 字符串解析结果
test_parse_expect_utf8_string() {
    char t1[2];
    t1[0] = 0x60;
    t1[1] = '\0';
    TEST_STRING(t1, "\"\\u0060\"");

    char t2[3];
    t2[0] = 0xd4;
    t2[1] = 0xac;
    t2[2] = '\0';
    TEST_STRING(t2, "\"\\u052c\"");

    char t3[4];
    t3[0] = 0xea;
    t3[1] = 0xa5;
    t3[2] = 0xb3;
    t3[3] = '\0';
    TEST_STRING(t3, "\"\\ua973\"");

    char t4[5];
    t4[0] = 0xf2;
    t4[1] = 0x9c;
    t4[2] = 0x8a;
    t4[3] = 0xac;
    t4[4] = '\0';
    TEST_STRING(t4, "\"\\uda30deac\"");

    char t5[5];
    t5[0] = 0XF4;
    t5[1] = 0X8F;
    t5[2] = 0XBF;
    t5[3] = 0XBF;
    t5[4] = '\0';
    TEST_STRING(t5, "\"\\udbffdfff\"");
}

//测试非法的 utf-8 字符串解析错误码
static void test_invalid_utf8_string() {
    TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\udbff\"");
    TEST_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE, "\"\\udbffe000\"");
}

/* TODO: 考虑写成宏的形式，在然后设计一个测试集合，对每个集合元素调用宏进行测试。*/
// 测试数组解析
static void test_parse_array() {
    TEST_ARRAY(0, "[ ]");
    TEST_ARRAY(5, "[null, false, true, 123, \"abc\"]");
    TEST_ARRAY(6, "[null, true, false, 123, \"abc\", [ [[0], 1], 2, 3] ]");
}

//解析测试
static void test_parse() {
#if 1
#    if 0
    test_parse_expect_literal();  //解析value字面量: true, false, null
    test_parse_invalid_literal();  //解析非法字面量
#    endif
#    if 0
    test_parse_expect_number();  //测试有效数字
    test_parse_invalid_number();  //测试非法数字
#    endif
#    if 0
#        if 1
    test_parse_expect_string();   //测试有效的不含 utf-8 字符的字符串
    test_parse_invalid_string();  //测试非法的不含 utf-8 字符的字符串
#        endif
#        if 1
    test_parse_expect_utf8_string();  //测试有效的 utf-8 字符串
    test_invalid_utf8_string();       //测试非法 utf-8 字符串
#        endif
#    endif
    test_parse_array();
#endif
}

int main(void) {
    /*
    typedef struct context{
        size_t size;  //堆栈大小
        size_t top;   //堆栈栈顶位置
        const char* json;
        char* stack;  //缓冲区堆栈
    } lept_context;
    lept_context c;
    c.size = 0;
    c.top = 0;
    c.stack = NULL;
    c.json = " ";
    lept_value v;
    v.type = LEPT_BOOLEAN;
    v.union_struct.boolean = 1;
    lept_context_stack_grow(&c, sizeof(lept_value));


    lept_value v;
    lept_init(&v);
    v.type = LEPT_NUMBER;
    v.union_struct.number = 123;
    void* p = &v;
    size_t i = 0;
    printf("\n");
    while (p + i < p + sizeof(v)) {
        printf("%x ", *((char*)p + i));
        i += sizeof(char);
    }
    printf("\n");
    return 0;
    */

    test_parse();  //测试解析
    if (test_count == 0) {
        printf("No test.\n");
        return main_ret;
    }
    //打印通过次数、总测试次数、通过率
    printf("%d / %d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count);
    return main_ret;
}